/**
 *  Copyright (c) 2010 The edtf project.  All rights reserved.
 *
 *  This file is part of edtf (Event Driven Testing Framework).
 *
 *  edtf is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  edtf is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with edtf.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  This software consists of voluntary contributions made by many
 *  individuals.  For exact contribution history, see the revision
 *  history and logs, available at http://code.google.com/p/edtf.
 */
package org.xepf.xml.saxon;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.Configuration;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.sxpath.IndependentContext;
import net.sf.saxon.sxpath.XPathEvaluator;
import net.sf.saxon.sxpath.XPathExpression;
import net.sf.saxon.trans.XPathException;
import org.xepf.xml.adapter.XPath;
import org.xepf.xml.adapter.XMLNode;

/**
 *
 * 
 */
public class XPathImp implements XPath {

    private NodeInfo inputNode;
    private List<XMLNode> resultNodeList;
    private XPathEvaluator xPathEvaluator;
    private XPathExpression xPathExpression;
    private IndependentContext context;
    private String xmlDocument;
    private String xPathQuery = "/";
    private Map<String, String> namespaceMap;

    public XPathImp() throws Exception {

        context = new IndependentContext();
        namespaceMap = new HashMap<String, String>();
    }

    private void setXml(String xmlDocument) {
        this.xmlDocument = xmlDocument;
    }

    private void setXPath(String xPathQuery) {
        this.xPathQuery = xPathQuery;
    }

    @Override
    public void setNamespaces(Map<String, String> namespaces) {
        namespaceMap.putAll(namespaces);
    }

    @Override
    public void initialize(String xmlDocument) throws Exception {
        setXml(xmlDocument);
        initialize(xmlDocument, xPathQuery);
    }

    @Override
    public void initialize(XMLNode xmlNode) throws Exception{
        this.inputNode = (NodeInfo) xmlNode.getNativeNode();
        xPathEvaluator = new XPathEvaluator();
    }

    private void initialize(String xmlDocument, String xPathQuery) throws Exception {
        xPathEvaluator = new XPathEvaluator();
        loadXml(xmlDocument);
        loadNamespaces();
        initializeIndependentContext(namespaceMap);
        initializeExpression(xPathQuery);
    }

    private void loadXml(String xmlDocument) throws XPathException, UnsupportedEncodingException {
        StreamSource source = new StreamSource(new ByteArrayInputStream(xmlDocument.getBytes("UTF-8")));
        Configuration config = xPathEvaluator.getConfiguration();
        inputNode = config.buildDocument(source);
    }

    private void loadNamespaces() throws Exception {
        // the namespaces used in the XML document should be retrieved
        // and placed on the static context
        xPathExpression = xPathEvaluator.createExpression("//namespace::*");
        List<XMLNode> namespaceNodes = query();
        for (XMLNode nodeInfo : namespaceNodes) {
            namespaceMap.put(nodeInfo.getLocalPart(), nodeInfo.getStringValue());
        }

        // this seems to be missing in some cases
        namespaceMap.put("xsd", "http://www.w3.org/2001/XMLSchema");
    }

    private void initializeIndependentContext(Map<String, String> namespaces) throws Exception {
        for (Map.Entry<String, String> namespaceEntry : namespaces.entrySet()) {
            String nsPrefix = namespaceEntry.getKey();
            String nsUri = namespaceEntry.getValue();
            if (nsPrefix.isEmpty()) {
                context.setDefaultElementNamespace(nsUri);
            } else {
                context.declareNamespace(nsPrefix, nsUri);
            }
        }
    }

    private void initializeExpression(String xPathQuery) throws Exception {
        xPathEvaluator.setStaticContext(context);
        xPathExpression = xPathEvaluator.createExpression(xPathQuery);
    }

    private List<XMLNode> query() throws Exception {
        return query(inputNode);
    }

    private List<XMLNode> query(NodeInfo inputXmlNode) throws Exception {
        resultNodeList = new LinkedList<XMLNode>();
        // this should return a list of NodeInfo or, if the result is an atomic value, a Java object.
        List<Object> nodeList = xPathExpression.evaluate( inputXmlNode);
        for (Object nodeInfo : nodeList) {
            XMLNode xmlNode = new XMLNodeImp(nodeInfo);
            resultNodeList.add(xmlNode);
        }
        return resultNodeList;
    }

    @Override
    public List<XMLNode> query(String xPathQuery) throws Exception {
        loadNamespaces();
        initializeIndependentContext(namespaceMap);
        xPathExpression = xPathEvaluator.createExpression(xPathQuery);
        return (List<XMLNode>) (List< ?>) query();
    }

    @Override
    public List<XMLNode> getQueryResult() {
        return (List<XMLNode>) (List< ?>) resultNodeList;
    }

    @Override
    public XMLNode getXmlDocumentNode() {
        return (XMLNode) inputNode;
    }
}
